﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DiscreteEventModel {

    using Common;

    public partial class DiscreteEvent<TContext> {

        public interface IEngine {
            IProcess Process { get; }
            bool Step();
        }

        partial class EngineProcess : IEngine, IComparer<Agent> {

            public IProcess Process {
                get {
                    return this;
                }
            }

            public int Compare(Agent agent1, Agent agent2) {
                return agent2.ActionTime.CompareTo(agent1.ActionTime);
            }

            public bool Step() {
                Agent activeAgent;
                if(!this._agentQueue.Peek(out activeAgent)) {
                    this._time = DiscreteTime.Base;
                    return false;
                }
                this._time = activeAgent.ActionTime;
                do {
                    #region Synchronize agent
                    double timeStep = this._time - activeAgent._stateTime;
                    if(timeStep > 0) {
                        activeAgent.Synchronize(timeStep);
                        activeAgent._stateTime = this._time;
                    }
                    #endregion
                    #region Execute agent
                    for(DiscreteEvent<TContext> activeEvent; activeAgent._eventQueue.Peek(out activeEvent) && activeEvent._time == this._time; activeAgent._eventQueue.Dequeue(activeEvent)) {
                        if(activeEvent == activeAgent) {
                            activeAgent.Output(this);
                        } else {
                            activeAgent.Input(activeEvent);
                        }
                    }
                    #endregion
                    #region Commit agent
                    double outputDelay = activeAgent.Commit(this._context);
                    if(double.IsPositiveInfinity(outputDelay)) {
                        activeAgent._time = DiscreteTime.Infinity;
                        activeAgent._eventQueue.Dequeue(activeAgent);
                    } else if(outputDelay > 0) {
                        activeAgent._time += outputDelay;
                        activeAgent._eventQueue.Enqueue(activeAgent);
                    } else {
                        throw new Exception();
                    }
                    if(activeAgent.ActionTime.IsInfinity) {
                        this._agentQueue.Dequeue(activeAgent);
                        activeAgent.Dispose(this._context);
                    } else {
                        this._agentQueue.Enqueue(activeAgent);
                    }
                    #endregion
                } while(this._agentQueue.Peek(out activeAgent) && activeAgent.ActionTime == this._time);
                return true;
            }
        }
    }
}
